/**
 * 
 */
package com.bitsetters.android.passwordsafe;

import java.io.File;
import java.util.List;

import android.content.Context;
import android.util.Log;

import com.db4o.Db4o;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.config.Configuration;
import com.db4o.query.Predicate;

/**
 * @author German Viscuso
 *
 */
public class DBHelper {

	//public static long latestId = 0;
	private static ObjectContainer oc = null;
	private Context context;

	private static boolean needsPrePopulation = false;

	/**
	   * 
	   * @param ctx
	   */
	public DBHelper(Context ctx) {
		this.context = ctx;
	}

	public void backup(String path) {
		this.db().ext().backup(path);
	}

	public void clearPrePopulate() {
		needsPrePopulation = false;
	}

	/**
	   * Close database connection
	   */
	public void close() {
		if (oc != null) {
			oc.close();
			oc = null;
		}
	}

	public void commit() {
		this.db().commit();
	}

	public int countPasswords(long Id) {
		ObjectSet<PassEntry> passwords = this.fetchPasswordsById(Id);
		return passwords == null ? 0 : passwords.size();
	}

	private ObjectContainer db() {
		try {
			if (oc == null || oc.ext().isClosed()) {
				oc = Db4o.openFile(this.dbConfig(), this.db4oDBFullPath(this.context));
			}
			return oc;
		} catch (Exception e) {
			Log.e(DBHelper.class.getName(), e.toString());
			return null;
		}
	}

	public String db4oDBFullPath(Context ctx) {
		return ctx.getDir("data", 0) + "/" + "android.db4o";
	}

	private Configuration dbConfig() {
		Configuration c = Db4o.newConfiguration();
		//Index entries by Id
		c.objectClass(PassEntry.class).objectField("id").indexed(true);
		//Configure proper activation + update depth
		//TODO
		return c;
	}

	/**
	 * 
	 * @param Id
	 */
	public void deleteCategory(long Id) {
		//Search by Id
		ObjectSet<CategoryEntry> result = this.fetchCategoriesById(Id);
		//Delete object
		while (result.hasNext()) {
			this.db().delete(result.next());
		}
		this.db().commit();
	}

	public void deleteDatabase() {
		this.close();
		new File(this.db4oDBFullPath(this.context)).delete();
	}

	/**
	 * 
	 * @param Id
	 */
	public void deletePassword(long Id) {
		//Search by Id
		ObjectSet<PassEntry> result = this.fetchPasswordsById(Id);
		//Delete object
		while (result.hasNext()) {
			this.db().delete(result.next());
		}
		this.db().commit();
	}

	public List<CategoryEntry> fetchAllCategoryRows() {
		return this.db().query(CategoryEntry.class);
	}

	/**
	 * 
	 * @return
	 */
	public List<PassEntry> fetchAllPassRows() {
		return this.db().query(PassEntry.class);
	}

	/**
	 * Fetch CategoryEntries by Id using db4o's Query By Example (QBE)
	 * @param Id
	 */
	private ObjectSet<CategoryEntry> fetchCategoriesById(long Id) {
		CategoryEntry ct = new CategoryEntry();
		ct.id = Id;
		return this.db().queryByExample(ct);
	}

	/**
	 * Fetch CategoryEntry by Id using db4o's Query By Example (QBE)
	 * @param Id
	 */
	public CategoryEntry fetchCategoryById(long Id) {
		ObjectSet<CategoryEntry> result = this.fetchCategoriesById(Id);
		if (result.hasNext()) {
			return result.next();
		}
		return null;
	}

	/**
	 * 
	 * @return
	 */
	public String fetchMasterKey() {
		ObjectSet<PBEKey> result = this.db().queryByExample(new PBEKey());
		if (result.hasNext()) {
			String key = (result.next()).getKey();
			if (key != null) {
				return key;
			}
		}
		return "";
	}

	/**
	 * 
	 * @param Id
	 * @return
	 */
	public PassEntry fetchPasswordById(long Id) {
		ObjectSet<PassEntry> result = this.fetchPasswordsById(Id);
		if (result.hasNext()) {
			return result.next();
		}
		return null;
	}

	/**
	 * Fetch PassEntry by Id using db4o's Query By Example (QBE)
	 * @param Id
	 */
	private ObjectSet<PassEntry> fetchPasswordsById(long Id) {
		PassEntry pe = new PassEntry();
		pe.id = Id;
		return this.db().queryByExample(pe);
	}

	/**
	 * 
	 * @return
	 */
	public List<PassEntry> fetchPasswordsForCategory(final CategoryEntry category) {
		List<PassEntry> result = this.db().query(new Predicate<PassEntry>() {
			@Override
			public boolean match(PassEntry entry) {
				if (entry.category.equals(category)) {
					return true;
				}
				return false;
			}
		});
		return result;
	}

	public String fullDesc() {
		String s = "";
		List<PassEntry> passwords = this.fetchAllPassRows();
		for (PassEntry pe : passwords) {
			s += Long.toString(pe.id) + "-" + pe.description + "\n";
		}
		return s;
	}

	public long getNextId() {
		ObjectSet<IncrementedId> result = this.db().queryByExample(IncrementedId.class);
		IncrementedId ii = null;
		long nextId;
		if (result.hasNext()) {
			ii = result.next();
		} else {
			ii = new IncrementedId();
		}
		nextId = ii.getNextID();
		this.db().store(ii);
		return nextId;
	}

	public boolean getPrePopulate() {
		return needsPrePopulation;
	}

	public int passwordCount() {
		List<PassEntry> passwords = this.fetchAllPassRows();
		return passwords == null ? 0 : passwords.size();
	}

	public void restore(String path) {
		this.deleteDatabase();
		new File(path).renameTo(new File(this.db4oDBFullPath(this.context)));
		new File(path).delete();
	}

	public void rollback() {
		this.db().rollback();
	}

	/**
	 * 
	 * @param entry
	 */
	public long saveCategory(CategoryEntry entry) {
		if (entry.id == 0) {
			entry.id = this.getNextId();
		}
		this.db().store(entry);
		this.db().commit();
		return entry.id;
	}

	/**
	   * 
	   * @param entry
	   */
	public void savePassword(PassEntry entry) {
		if (entry.id == 0) {
			entry.id = this.getNextId();
		}
		this.db().store(entry);
		this.db().commit();
	}

	/**
	 * 
	 * @param PBEKey
	 */
	public void storeMasterKey(String pbekey) {
		ObjectSet<PBEKey> result = this.db().queryByExample(new PBEKey());
		PBEKey key;
		if (result.hasNext()) {
			key = result.next();
			key.setKey(pbekey);
		} else {
			key = new PBEKey(pbekey);
		}
		this.db().store(key);
		this.db().commit();
	}

}
